package com.jfl.core.shiro;

import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.crazycake.shiro.IRedisManager;
import org.crazycake.shiro.serializer.RedisSerializer;
import org.crazycake.shiro.serializer.StringSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.alibaba.dubbo.config.annotation.Reference;
import com.google.common.collect.Lists;
import com.jfl.common.Constants;
import com.jfl.common.MessageKey;
import com.jfl.config.Resources;
import com.jfl.core.utils.ShiroUtils;
import com.jfl.model.SysSession;
import com.jfl.model.SysUser;
import com.jfl.system.SysMenuService;
import com.jfl.system.SysSessionService;
import com.jfl.system.SysUserService;
import com.jfl.utils.DateUtil;
import com.jfl.utils.EncryptUtils;
import com.jfl.utils.IpUtil;
import com.jfl.utils.ServletUtil;

/**
 * 自定义Realm 组件
 * 
 * @author 赵炎
 * @version [V1.00, 2018年12月5日]
 * @since V1.00
 */
@Component
public class CustomRealm extends AuthorizingRealm
{
    /**
     * 系统日志
     */
    private static final Logger LOG = LoggerFactory.getLogger(CustomRealm.class);
    
    @Reference(version = "${jfl.version}")
    private SysUserService sysUserService;
    
    @Reference(version = "${jfl.version}")
    private SysSessionService sysSessionService;
    
    @Reference(version = "${jfl.version}")
    private SysMenuService sysMenuService;
    
    @Value("${jfl.shiro.cache.prefix}")
    private String keyPrefix;
    
    @SuppressWarnings("rawtypes")
    private RedisSerializer keySerializer = new StringSerializer();
    
    @Autowired
    private IRedisManager redisManager;
    
    /**
     * 重写方法
     * 
     * @param principals
     * @return
     * @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
    {
        if (CustomRealm.LOG.isDebugEnabled())
        {
            LOG.debug("==> 授权认证 start...");
        }
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        SysUser user = ShiroUtils.getCurrentUser();
        // 管理员
        if (user.getIsAdmin() == 1)
        {
            authorizationInfo.addRole("admin");
            authorizationInfo.addStringPermission("*:*:*");
        }
        // 普通用户
        else
        {
            authorizationInfo.addRole("user");
            List<String> records = this.sysMenuService.getPermissionsByUserId(user.getId());
            List<String> permissions = Lists.newArrayList();
            if (records != null && records.size() > 0)
            {
                for (String permission : records)
                {
                    if (StringUtils.isNotBlank(permission))
                    {
                        permissions.add(permission);
                    }
                }
                if (!permissions.isEmpty())
                {
                    authorizationInfo.addStringPermissions(permissions);
                }
                
            }
            
        }
        
        if (CustomRealm.LOG.isDebugEnabled())
        {
            LOG.debug("==> 授权认证 end...");
        }
        return authorizationInfo;
    }
    
    /**
     * 重写方法
     * 
     * @param token
     * @return
     * @throws AuthenticationException
     * @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
    {
        
        if (CustomRealm.LOG.isDebugEnabled())
        {
            LOG.debug("==> 登录验证 start...");
        }
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;
        String userName = usernamePasswordToken.getUsername();
        
        SysUser sysUser = this.sysUserService.selectByUserName(userName);
        if (sysUser != null)
        {
            // 验证码校验
            if (!org.springframework.util.StringUtils.isEmpty(ServletUtil.getRequest().getAttribute("captcha")))
            {
                throw new AuthenticationException("验证码错误！");
            }
            if (sysUser != null && Constants.STATUS_LOCKED.equals(sysUser.getStatus()))
            {
                throw new LockedAccountException("该账号已锁，请联系管理员！");
            }
            String password = null;
            if (usernamePasswordToken.getPassword() != null)
            {
                password = new String(usernamePasswordToken.getPassword());
            }
            if (sysUser.getPassword().equals(EncryptUtils.encryptPassword(userName, password, sysUser.getSalt())))
            {
                
                // save session
                // ShiroUtils.setCurrentUser(sysUser);
                // token中host信息
                sysUser.setLoginDate(DateUtil.getCurrentDate());
                sysUser.setLoginIp(IpUtil.getHostIp());
                saveSession(sysUser, usernamePasswordToken.getHost());
                // 注意：参数设置问题 否则会出现Redis异常
                AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(sysUser, password, sysUser.getUserName());
                this.sysUserService.update(sysUser);
                return authcInfo;
            }
            else
            {
                LOG.warn("User [{}] password is wrong:{}", userName, password);
                throw new AuthenticationException(Resources.getMessage(MessageKey.USERNAME_OR_PASSWORD_ERROR));
            }
            
        }
        else
        {
            LOG.warn("==> No user:{}... ", userName);
            throw new AuthenticationException(Resources.getMessage(MessageKey.USER_NO_EXIST));
        }
    }
    
    /**
     * 保存session
     * 
     * @param userName
     * @param host
     */
    private void saveSession(SysUser sysUser, String host)
    {
        
        SysSession session = new SysSession();
        session.setUserName(sysUser.getUserName());
        List<SysSession> list = this.sysSessionService.querySessionByUserName(sysUser.getUserName());
        // 当前用户session
        Session currentSession = ShiroUtils.getSession();
        // 用户Id
        String currentSessionId = (String)currentSession.getId();
        if (list != null)
        {
            for (SysSession sysSession : list)
            {
                session.setSessionId(sysSession.getSessionId());
                if (!currentSessionId.equals(sysSession.getSessionId()))
                {
                    deleteSession(sysSession.getSessionId());
                }
            }
            
        }
        session.setCreateBy(sysUser.getId());
        session.setUpdateBy(sysUser.getId());
        session.setSessionId(currentSessionId);
        session.setIp(StringUtils.isBlank(host) ? currentSession.getHost() : host);
        session.setStartTime(currentSession.getStartTimestamp());
        this.sysSessionService.update(session);
    }
    
    /**
     * 删除session
     * 
     * @param sessionId
     */
    @SuppressWarnings("unchecked")
    public void deleteSession(String sessionId)
    {
        try
        {
            this.redisManager.del(keySerializer.serialize(getRedisSessionKey(sessionId)));
            // 删除数据库中session
            this.sysSessionService.deleteBySessionId(sessionId);
        }
        catch (Exception e)
        {
            LOG.error("序列化异常", e);
        }
        
        
    }
    
    /**
     * 清理缓存权限
     */
    public void clearCachedAuthorizationInfo()
    {
        this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
    }
    
    private Object getRedisSessionKey(String id)
    {
        return keyPrefix + id;
    }
    
}
